package besta.moon.engine.selector.result;

import java.sql.*;
import java.util.List;

import besta.moon.MoonConnection;
import besta.moon.engine.Moon;
import besta.moon.engine.Table;
import besta.moon.engine.View;
import besta.moon.engine.store.MoonImpl;
import besta.moon.engine.store.MoonNull;
import besta.moon.engine.store.MoonPageLink;
import besta.moon.engine.table.Column;
import besta.moon.engine.table.Columns;
import besta.moon.sql.Expression;
import besta.moon.sql.datatype.MutableNumeric;

public final class TableResult extends TableViewResult {

    final private Table table;

    private List<MoonPageLink> insertStorePages;

    private long firstOwnInsert;
  
    private long maxFileOffset;

    TableResult(Table table) {
        this.table = table;
    }

    @Override
    public final boolean init(MoonConnection con) throws Exception {
        if (super.init(con)) {
            Columns columns = table.columns;
            offsets = new int[columns.size()];
            dataTypes = new int[columns.size()];
            for (int i = 0; i < columns.size(); i++) {
                dataTypes[i] = columns.get(i).getDataType();
            }
            return true;
        }
        return false;
    }

    @Override
    public final void execute() throws Exception {
        insertStorePages = table.getInserts(con);
        firstOwnInsert = 0x4000000000000000L | insertStorePages.size();
        maxFileOffset = table.raFile.size();
        beforeFirst();
    }

    @Override
    public final View getTableView() {
        return table;
    }

    @Override
    public final void deleteRow() throws SQLException {
        store.deleteRow(con);
        store = new MoonNull(store.getNextPagePos());
    }

    @Override
    public final void updateRow(Expression[] updateValues) throws Exception {
        Columns tableColumns = table.columns;
        int count = tableColumns.size();

        MoonImpl newStore = table.getStoreTemp(con);
        synchronized (con.getMonitor()) {
            ((MoonImpl) this.store).createWriteLock();

            for (int i = 0; i < count; i++) {
                Expression src = updateValues[i];
                if (src != null) {
                    newStore.writeExpression(src, tableColumns.get(i));
                } else {
                    copyValueInto(i, newStore);
                }
            }
            ((MoonImpl) this.store).updateFinsh(con, newStore);
        }
    }

    @Override
    public final void insertRow(Expression[] updateValues) throws Exception {
        Columns tableColumns = table.columns;
        int count = tableColumns.size();

        MoonImpl store = table.getStoreInsert(con);
        for (int i = 0; i < count; i++) {
            Column tableColumn = tableColumns.get(i);
            Expression src = updateValues[i];
            if (src == null) {
                src = tableColumn.getDefaultValue(con);
            }
            store.writeExpression(src, tableColumn);

        }
        store.writeFinsh(con);
        insertStorePages.add(store.getLink());
    }

    private Moon store = Moon.NOROW;

    private long filePos;
    private int[] offsets;
    private int[] dataTypes;
    private int row;

    private long afterLastValidFilePos;

    final private boolean moveToRow() throws Exception {
        if (filePos >= 0x4000000000000000L) {
            store = ((MoonPageLink) insertStorePages.get((int) (filePos & 0x3FFFFFFFFFFFFFFFL))).getStore(table, con,
                    lock);
        } else {
            store = (filePos < maxFileOffset) ? table.getStore(con, filePos, lock) : null;
            if (store == null) {
                if (!insertStorePages.isEmpty()) {
                    filePos = 0x4000000000000000L;
                    store = ((MoonPageLink) insertStorePages.get((int) (filePos & 0x3FFFFFFFFFFFFFFFL))).getStore(
                            table, con, lock);
                }
            }
        }
        if (store != null) {
            if (!store.isValidPage()) {
                return false;
            }
            store.scanObjectOffsets(offsets, dataTypes);
            afterLastValidFilePos = store.getNextPagePos();
            return true;
        } else {
            filePos = -1;
            noRow();
            return false;
        }
    }

    private boolean moveToValidRow() throws Exception {
        while (filePos >= 0) {
            if (moveToRow()) {
                return true;
            }
            setNextFilePos();
        }
        row = 0;
        return false;
    }

    @Override
    public final void beforeFirst() {
        filePos = 0;
        store = Moon.NOROW;
        row = 0;
    }

    @Override
    public final boolean first() throws Exception {
        filePos = table.getFirstPage();
        row = 1;
        return moveToValidRow();
    }

    private void setNextFilePos() {
        if (filePos < 0) {
            return; 
        }
        if (store == Moon.NOROW) {
            filePos = table.getFirstPage();
        } else if (filePos >= 0x4000000000000000L) {
            filePos++;
            if ((filePos & 0x3FFFFFFFFFFFFFFFL) >= insertStorePages.size()) {
                filePos = -1;
                noRow();
            }
        } else {
            filePos = store.getNextPagePos();
        }
    }

    @Override
    public final boolean next() throws Exception {
        if (filePos < 0) {
            return false;
        }
        setNextFilePos();
        row++;
        return moveToValidRow();
    }

    @Override
    public final void afterLast() {
        filePos = -1;
        noRow();
    }

    @Override
    public final int getRow() {
        return row;
    }

    @Override
    public final long getRowPosition() {
        return filePos;
    }

    @Override
    public final void setRowPosition(long rowPosition) throws Exception {
        filePos = rowPosition;
        if (filePos < 0 || !moveToRow()) {
            store = new MoonNull(store.getNextPagePos());
        }
    }

    @Override
    public final boolean rowInserted() {
        return filePos >= firstOwnInsert;
    }

    @Override
    public final boolean rowDeleted() {

        if (store instanceof MoonNull && store != Moon.NULL) {
            return true;
        }
        return store instanceof MoonImpl && ((MoonImpl) store).isRollback();
    }

    @Override
    public final void nullRow() {
        row = 0;
        store = Moon.NULL;
    }

    @Override
    public final void noRow() {
        row = 0;
        store = Moon.NOROW;
    }

    @Override
    public final boolean isNull(int colIdx) throws Exception {
        return store.isNull(offsets[colIdx]);
    }

    @Override
    public final boolean getBoolean(int colIdx) throws Exception {
        return store.getBoolean(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final int getInt(int colIdx) throws Exception {
        return store.getInt(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final long getLong(int colIdx) throws Exception {
        return store.getLong(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final float getFloat(int colIdx) throws Exception {
        return store.getFloat(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final double getDouble(int colIdx) throws Exception {
        return store.getDouble(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final long getMoney(int colIdx) throws Exception {
        return store.getMoney(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final MutableNumeric getNumeric(int colIdx) throws Exception {
        return store.getNumeric(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final Object getObject(int colIdx) throws Exception {
        return store.getObject(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final String getString(int colIdx) throws Exception {
        return store.getString(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final byte[] getBytes(int colIdx) throws Exception {
        return store.getBytes(offsets[colIdx], dataTypes[colIdx]);
    }

    @Override
    public final int getDataType(int colIdx) {
        return dataTypes[colIdx];
    }

    private void copyValueInto(int colIdx, MoonImpl dst) {
        int offset = offsets[colIdx++];
        int length = (colIdx < offsets.length ? offsets[colIdx] : store.getUsedSize()) - offset;
        dst.copyValueFrom((MoonImpl) store, offset, length);
    }

}
